package com.oromwin.repos.server.request.file;

import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import org.apache.commons.logging.Log;

import com.oromwin.repos.common.Common;
import com.oromwin.repos.common.Logger;
import com.oromwin.repos.common.ReposConfig;
import com.oromwin.repos.common.ReposException;
import com.oromwin.repos.common.Common.Category;
import com.oromwin.repos.common.helper.DBHelper;
import com.oromwin.repos.common.helper.FSHelper;
import com.oromwin.repos.common.helper.IOHelper;
import com.oromwin.repos.data.AbstractReposTable;
import com.oromwin.repos.data.FileTable;
import com.oromwin.repos.data.MediaTable;
import com.oromwin.repos.server.LockObject;
import com.oromwin.repos.server.request.AbstractRequest;
import com.oromwin.repos.server.response.CreateResponse;
import com.oromwin.repos.server.response.Response;

/*
 * reflection
 * XXX: must flush, if not packet exists in local network buffer
 * XXX: must not use ObjectInputStream before output stream... freezing socket
 */
public class CreateFileRequest extends AbstractRequest {
    private transient Log log = Logger.getLog(CreateFileRequest.class);
    private String VolumeId = null;
    private String FileName = null;
    private String FileFormat = null;
    private long FileBytes = 0L;
    // other info.
    private File DstFilePath = null;
    private File TempFilePath = null;
    
    @Override
    public void receive(DataInputStream dis, LockObject lockObject) throws ReposException {
        try {
            synchronized (lockObject) {
                VolumeId = IOHelper.readUTF8(dis);
                FileName = IOHelper.readUTF8(dis);
                FileFormat = Common.getFileFormat(FileName);
                FileBytes = dis.readLong();
                getCurrentReposTables();
                checkFreeSpaceExists();
                // receive file
                File mediaPath = _mediaTable.getMediaPath();
                DstFilePath = Common.FilePath.generate(mediaPath, id());
                checkSameObjectExists();
                TempFilePath = new File(DstFilePath + File.separator + id());
                int off = 0;
                int len = ReposConfig.getInstance().getReposSocketIOBuffSize();
                int read = 0;
                byte[] b = new byte[len];
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(TempFilePath));
                while (read < FileBytes) {
                    int readBytes = dis.read(b, off, len);
                    read += readBytes;
                    bos.write(b, off, readBytes);
                }
                bos.flush();
                bos.close();
            }
        } catch (IOException e) {
            String message = String.format("Failed to read stream, {%s}", e.getMessage());
            throw ReposException.create(log, e, message);
        }
    }

    public Response run() throws ReposException {
        CreateResponse res = null;
        try {
            String volumeId = _mediaTable.getVolumeId();
            String mediaId = _mediaTable.getMediaId();
            String fileId = id();
            String fileCategory = Category.NORMAL;
            Boolean fileStatus = Boolean.FALSE;
            Boolean pdfConvFlag = false;
            Boolean compressionFlag = false;
            _fileTable = new FileTable(volumeId, mediaId, fileId, FileName, FileFormat, DstFilePath, FileBytes, fileCategory, fileStatus, pdfConvFlag, compressionFlag, Common.datetime(), Common.datetime());
            DBHelper.insertTable(_fileTable);
            String whereClause = String.format("VolumeId = '%s' AND MediaId = '%s'", _fileTable.getVolumeId(), _fileTable.getMediaId());
            List<AbstractReposTable> fileTables = DBHelper.selectTable(FileTable.class, whereClause);
            int mediaFileCount = 0;
            long mediaUsedBytes = 0L;
            for (AbstractReposTable table : fileTables) {
                if (table instanceof FileTable) {
                    FileTable fileTable = (FileTable) table;
                    mediaFileCount++;
                    mediaUsedBytes += fileTable.getFileBytes();
                }
            }
            _mediaTable.setMediaUsedBytes(mediaUsedBytes);
            _mediaTable.setMediaFileCount(mediaFileCount);
            DBHelper.updateTable(_mediaTable, whereClause);
            res = new CreateResponse(true, "Succeeded to create file", fileId);
        } catch (ReposException e) {
            res = new CreateResponse(false, e.getMessage(), id());
        } catch (Exception e) {
            String message = String.format("Unexpected exception, {%s}", e.getMessage());
            throw ReposException.create(log, e, message);
        }
        return res;
    }

    @Override
    public void checkFreeSpaceExists() throws ReposException {
        // 1. 해당 미디어에 저장할 공간이 있는지 확인
        long freeBytes = _mediaTable.getMediaFreeBytes();
        if (freeBytes < FileBytes) {
            String message = String.format("Requested file size is too big, freespace:requested {%d:%d}", freeBytes, FileBytes);
            throw ReposException.create(log, message);
        }
    }

    @Override
    public void checkSameObjectExists() throws ReposException {
        // 1. 동일한 파일명이 있는지 확인
        File file = new File(DstFilePath, id());
        if (FSHelper.ls(file)) {
            String message = String.format("Same filename already exist on the server, path {%s}", file);
            throw ReposException.create(log, message);
        }
    }

    @Override
    public void getCurrentReposTables() throws ReposException {
        // 1. 대상 미디어 정보를 데이터베이스에서 가져옴 - 여러개가 있다면 여유공간이 많은 미디어를 선택
        String whereClause = String.format("VolumeId = '%s' AND MediaStatus = 0 ", VolumeId);
        List<AbstractReposTable> dstReposTables = DBHelper.selectTable(MediaTable.class, whereClause);
        if (dstReposTables != null && dstReposTables.size() == 1) {
            AbstractReposTable TempTable = dstReposTables.get(0);
            if (TempTable instanceof MediaTable) {
                _mediaTable = (MediaTable) TempTable;
            } else {
                String message = String.format("Illegal media table type, class {%s}", TempTable.getClass().toString());
                throw ReposException.create(log, message);
            }
        } else if (dstReposTables.size() > 0) {
            // 여러개가 있다면 용량이 큰 미디어를 선택
            long freeBytes = 0;
            for (AbstractReposTable TempTable : dstReposTables) {
                if (TempTable instanceof MediaTable) {
                    MediaTable TempMediaTable = (MediaTable) TempTable;
                    long mediaBytes = TempMediaTable.getMediaFreeBytes(); 
                    if (freeBytes < mediaBytes) {
                        freeBytes = mediaBytes;
                        _mediaTable = TempMediaTable;
                    }
                } else {
                    String message = String.format("Illegal media table type, class {%s}", TempTable.getClass().toString());
                    throw ReposException.create(log, message);
                }
            }
        } else {
            String message = String.format("'%s' VolumeId does not exists in database, whereClause {%s}", VolumeId, whereClause);
            throw ReposException.create(log, message); 
        }
    }
    
}
